/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.core.net.protocols;

import hermesV2.Envelope;
import hermesV2.PlaceAddress;
import hermesV2.PlaceDescriptor;
import hermesV2.ServiceDescriptor;

import hermesV2.core.id.Trace;

import hermesV2.core.net.InputRequest;
import hermesV2.core.net.SendReceive;

import hermesV2.core.starter.Starter;

import hermesV2.util.Descriptor;
import hermesV2.util.HermesLogger;
import hermesV2.util.VectorQueue;

import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLServerSocket;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Properties;

/**
 * Manages the communications with other hosts, can send and receive messages.
 * 
 * @author Diego Bonura
 */
public class SendReceiveSSL extends SendReceive {
	
	/** The current place descriptor */
	private PlaceDescriptor currentPlaceDescriptor;

	/** The interface of the <code>AgentStarter</code> component. */
	private Starter starter;

	/** The interface of the Id component. */
	private Trace trace;

	/** The properties of the current place. */
	private Properties hermes_properties;

	/** The properties of the net component. */
	private Properties net_properties;

	/** This queue contains the envelopes for the basicServices Layer. */
	private VectorQueue<Envelope<?>> envelopeQueue;

	/** The timeOut (millisenconds) of the current for the outgoing connections */
	private int timeOut;
	/** This queue contains the placeDescriptors for the basicServices Layer. */
	private VectorQueue<PlaceDescriptor> descriptorQueue;

	/**
	 * Creates a new <code>SendReceive</code> object.
	 * 
	 * @param hermes_properties
	 *            Properties obj of the current place.
	 * @param propertyFileName
	 *            The file name of the net component.
	 */
	public SendReceiveSSL(String propertyFileName, Properties hermes_properties) {
		try {
			net_properties = loadProperties(propertyFileName);
		} catch (Exception e) {
			HermesLogger.log(HermesLogger.WARNING,
					"Errors loading net property file", e);
			System.exit(1);
		}
        PlaceAddress currentPlaceAddress;
		if (net_properties.getProperty("HERMESNET.AUTOLOADIP").equals("false"))
			currentPlaceAddress = new PlaceAddress
			  (net_properties.getProperty("HERMESNET.IP"),
			   Integer.parseInt(net_properties.getProperty("HERMESNET.PORT")),
			   net_properties.getProperty("HERMESNET.PHONENUMBER"));
		else
			currentPlaceAddress = new PlaceAddress
			  (getLocalIp(), 
			   Integer.parseInt(net_properties.getProperty("HERMESNET.PORT")),
			   net_properties.getProperty("HERMESNET.PHONENUMBER"));
			
		this.timeOut = Integer.parseInt(net_properties
				.getProperty("HERMESNET.TIMEOUT"));
		this.currentPlaceDescriptor = new PlaceDescriptor(net_properties.getProperty("HERMESNET.PLACENAME"),currentPlaceAddress,net_properties.getProperty("HERMESNET.HERMESNET.PLACEDESC"));
		this.hermes_properties = hermes_properties;
		envelopeQueue = new VectorQueue<Envelope<?>>();
		descriptorQueue = new VectorQueue<PlaceDescriptor>();
		new Thread(this).start();
	}

	/**
	 * Sets the interface of the <code>AgentStarter</code> component. It needs
	 * this interface when a new agents enter.
	 * 
	 * @param starter
	 *            The interface of the <code>AgentStarter</code> component.
	 */
	public void setStarter(Starter starter) {
		this.starter = starter;
	}

	/**
	 * Sets the Trace component interface.
	 * 
	 * @param trace
	 *            The Trace interface of the Id component.
	 */
	public void setTrace(Trace trace) {
		this.trace = trace;
	}

	/**
	 * The server starts.
	 */
	public void run() {

		SSLServerSocketFactory factory;
		SSLServerSocket serverSocket = null;
		String[] cipherSuite = { "SSL_DH_anon_WITH_RC4_128_MD5",
				"SSL_DH_anon_WITH_RC4_128_MD5",
				"SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
				"SSL_DH_anon_WITH_DES_CBC_SHA",
				"SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
				"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA" };

		try {
			factory = (SSLServerSocketFactory) SSLServerSocketFactory
					.getDefault();
			serverSocket = (SSLServerSocket) factory
					.createServerSocket(currentPlaceDescriptor.getPlaceAddress().getPort());
			serverSocket.setEnabledCipherSuites(cipherSuite);

		} catch (IOException ioe) {
			HermesLogger.log(HermesLogger.WARNING, "The server can't start!",
					ioe);
			System.exit(1);
		}

		HermesLogger.log(HermesLogger.INFO, getClass().getName()
				+ ": running at ip:"+currentPlaceDescriptor.getPlaceAddress().getIp()+" port:" + currentPlaceDescriptor.getPlaceAddress().getPort());
		
		//Main component loop
		while (true) {
			try {
				new InputRequest(serverSocket.accept(), currentPlaceDescriptor.getPlaceAddress(),
						this, starter, trace, envelopeQueue,descriptorQueue, hermes_properties);
			} catch (IOException ioe) {
				HermesLogger.log(HermesLogger.WARNING,
						"Error accepting a new socket", ioe);
			}
		}
	}

	/**
	 * When an agent entered needs new classes to be resolved, this methos
	 * search the class through the net.
	 * 
	 * @param classFileName
	 *            The name of the class to search plus UID.
	 * @param receiverPlaceAddress
	 *            The address of the remote place where to search new classes.
	 * 
	 * @return The class's bytes or null.
	 */
	public byte[] getClass(String classFileName,
			PlaceAddress receiverPlaceAddress) {
		HermesLogger.log(HermesLogger.DEBUG, "Try to find the class file:"
				+ classFileName + " on : " + receiverPlaceAddress);

		Object o = null;

		try {
			OutputRequestSSL or = new OutputRequestSSL(receiverPlaceAddress,
					new Envelope<String>(Envelope.CLASS_REQUEST, classFileName,
					        currentPlaceDescriptor.getPlaceAddress(), receiverPlaceAddress),
					this.timeOut);

			if (((or.getReply())).getType() == Envelope.CLASS_NOTFOUND) {
				HermesLogger.log(HermesLogger.WARNING,
						"I didn't find the class file:" + classFileName
								+ " on : " + receiverPlaceAddress);
			}

			if (((or.getReply())).getType() == Envelope.CLASS_ENTER) {
				HermesLogger.log(HermesLogger.DEBUG, "I found the class file:"
						+ classFileName + " on : " + receiverPlaceAddress);
				o = ((or.getReply()).getObject());
			}
		} catch (IOException ioe) {
			HermesLogger.log(HermesLogger.WARNING, "IOException", ioe);
		} catch (ClassNotFoundException cnfe) {
			HermesLogger.log(HermesLogger.WARNING, "ClassNotFoundException",
					cnfe);
		}

		return (byte[]) o;
	}

	/**
	 * Sends new envelope to a Place.
	 * 
	 * @param env
	 *            The envelope to send.
	 * 
	 * @return the answer from the remote Place.
	 * 
	 * @throws IOException
	 *             Something wrong with the connection.
	 */
	public Envelope<?> sendEnvelope(Envelope<?> env) throws IOException {    
	    OutputRequestSSL or = null;
	    Envelope<?> e = null;
		or = new OutputRequestSSL(env
				.getReceiverPlaceAddress(), env, this.timeOut);
		e=or.getReply();
		if (e==null){
	        throw new IOException("OutputRequest:Something wrong with the connection to: "+env.getReceiverPlaceAddress());
	    }
		return e;
	}

	/**
	 * Returns new Envelope waiting for the basicService layer requests. If
	 * there is no more envelope in the queue the caller will be set to a wait
	 * state until new envelope comes.
	 * 
	 * @return The first envelope in the envelopeQueue.
	 */
	public Envelope<?> receiveEnvelopeBlocking() {
        Envelope<?> env = null;
        try {
            env = envelopeQueue.pull();
        } catch (InterruptedException ie) {
            HermesLogger.log(HermesLogger.WARNING, "Interrupted Exception", ie);
        }
        return env;
    }


	/**
	 * Returns the current place descriptor.
	 * 
	 * @return the current placeDescriptor.
	 */
	public PlaceDescriptor getCurrentPlaceDescriptor() {
		return currentPlaceDescriptor;
	}

	/**
	 * Loads the properties from the net property file.
	 * 
	 * @param propertyFileName
	 *            The property file of the logger.
	 * @return The property object pf the net component.
	 * @throws FileNotFoundException
	 *             The logger file property dosen't exist.
	 * @throws IOException
	 *             Problems reading the property file of the logger.
	 * @throws Exception
	 *             Some parameters into the property file of the logger is
	 *             corrupted.
	 */
	private Properties loadProperties(String propertyFileName)
			throws FileNotFoundException, IOException, Exception {
		/* Loads the logger property file */
		Properties net_properties = new Properties();
		InputStream fis = new FileInputStream(propertyFileName);
		net_properties.load(fis);

		/* Checks the port property */
		if (net_properties.getProperty("HERMESNET.PORT") == null) {
			throw new Exception(
					"HERMESNET.PORT property doesn't exist in the property file:"
							+ propertyFileName);
		} else {
			//checks if HERMES.PORT contains a valid value
		    try {
				if (Integer.parseInt(net_properties.getProperty("HERMESNET.PORT")) < 1) {
					throw new Exception(
							"HERMESNET.PORT property is corrupt; must be > 0 in the property file:"
									+ propertyFileName);
				}} catch (NumberFormatException nfe) {
	                throw new Exception(
	                        "HERMESNET.PORT property is corrupt:"
	                                + propertyFileName);
	            }
		}

		/* Check the autoload ip property */
		if (!net_properties.getProperty("HERMESNET.AUTOLOADIP").equals("true")
				&& !net_properties.getProperty("HERMESNET.AUTOLOADIP").equals(
						"false")) {
			throw new Exception(
					"HERMES.AUTOLOADIP property must be true or false (LowerCase)");
		}

		if (net_properties.getProperty("HERMESNET.AUTOLOADIP").equals("false")) {
			/* Checks the ip property */
			if (net_properties.getProperty("HERMESNET.IP") == null) {
				throw new Exception(
						"HERMESNET.IP property doesn't exist in the property file:"
								+ propertyFileName);
			}
		}

		/* Checks the port property */
		if (net_properties.getProperty("HERMESNET.TIMEOUT") == null) {
			throw new Exception(
					"HERMESNET.TIMEOUT property doesn't exist in the property file:"
							+ propertyFileName);
		} else {
			//checks if HERMES.PORT contains a valid value
			if (Integer.parseInt(net_properties
					.getProperty("HERMESNET.TIMEOUT")) < 1) {
				throw new Exception(
						"HERMESNET.TIMEOUT property is corrupt; must be > 0 in the property file:"
								+ propertyFileName);
			}
		}
		
		if (net_properties.getProperty("HERMESNET.PLACENAME") == null) {
			throw new Exception(
					"HERMESNET.PLACENAME property doesn't exist in the property file:"
							+ propertyFileName);
		}
		if (net_properties.getProperty("HERMESNET.PLACEDESC") == null) {
			throw new Exception(
					"HERMESNET.PLACEDESC property doesn't exist in the property file:"
							+ propertyFileName);
		}

		return net_properties;
	}
	
	/**
	 * Loads automatically the ip of the local interface card. It tries to find a different
	 * address from the loopBack
	 * @return The ip address of the current machine.
	 */
	private String getLocalIp() {
		Enumeration<NetworkInterface> interfaces = null;
		String foundIp = "127.0.0.1";
		try {
			interfaces = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException se) {
			HermesLogger.log(HermesLogger.WARNING,
					"Unable to search the if ip", se);
		}
		boolean found = false;
		while (interfaces.hasMoreElements() && !found) {
			NetworkInterface nif = interfaces.nextElement();
			Enumeration<InetAddress> addresses = nif.getInetAddresses();
			while (addresses.hasMoreElements()) {
				InetAddress addr = addresses.nextElement();
				if (!addr.isLoopbackAddress()) {
					found = true;
					foundIp = addr.getHostAddress();
				}
			}
		}
		return foundIp;
	}
	
	/**
	 * Returns new PlaceDescriptor waiting for the basicService layer requests. If
	 * there is no more PlaceDescriptor in the queue the caller will be set to a wait
	 * state until new PlaceDescriptor comes.
	 * 
	 * @return The first Descriptor in the descriptorQueue.
	 */
	public Descriptor receiveDescriptorBloking() {
		Descriptor pd = null;
		try {
			pd = (Descriptor) descriptorQueue.pull();
		} catch (InterruptedException ie) {
			HermesLogger.log(HermesLogger.WARNING, "Interrupted Exception", ie);
		}
		return pd;
	}
	
	public void propagateServiceDescriptor(ServiceDescriptor sd){
	    HermesLogger.log(HermesLogger.WARNING, "Not yet implemented on Tcp protocol");
	}

}